#include <jni.h>
#include "com_example_soundtouchdemo_NativeSoundTouch.h"
#include "SoundTouch.h"
#include "SoundTouchStream.h"
#include "log.h"




static void convertInput(jbyte* input, float* output, const int buffer_size,
						 int bytesPerSample);
static int write(const float *bufferIn,  int numElems,
                 int bytesPerSample, jbyte* outdata);
static inline int saturate(float fvalue, float minval, float maxval);

static SoundTouchStream* getTouch(JNIEnv *env, jobject obj) {

	jclass cls = env->GetObjectClass(obj);
	jfieldID fid = env->GetFieldID(cls, "nativeSoundTouch", "J");
	SoundTouchStream* soundTouch =
            (SoundTouchStream*) (env->GetLongField(obj, fid));

	return soundTouch;
}

/*
 * Class:     com_example_soundtouchdemo_NativeSoundTouch
 * Method:    soundTouchCreate
 * Signature: ()J
 */
jlong Java_com_audiocap_NativeSoundTouch_soundTouchCreate(
		JNIEnv *env, jobject obj, jint sampleRate, jint channel, jint bytesPerSample) {

	SoundTouchStream* soundTouch = new SoundTouchStream();

	const char* verStr = soundTouch->getVersionString();
	LOGD("Create SoundTouch object[version: %s]", verStr);

	soundTouch->setSampleRate(sampleRate);
    LOGD("Create SoundTouch setChannels 2");
	soundTouch->setChannels(channel);
    soundTouch->setBytesPerSample(bytesPerSample);
	//soundTouch->setPitchSemiTones(0.00f);

	soundTouch->setSetting(SETTING_USE_QUICKSEEK, 0);

	soundTouch->setSetting(SETTING_SEQUENCE_MS, 32);
	soundTouch->setSetting(SETTING_SEEKWINDOW_MS, 15);
	soundTouch->setSetting(SETTING_OVERLAP_MS, 8);
	soundTouch->setSetting(SETTING_USE_AA_FILTER, 1);
    //LOGD("Create SoundTouch end");
	return (jlong) soundTouch;
}

/*
 * Class:     com_example_soundtouchdemo_NativeSoundTouch
 * Method:    soundTouchDestory
 * Signature: ()V
 */
void Java_com_audiocap_NativeSoundTouch_soundTouchDestory(
		JNIEnv *env, jobject obj) {

	SoundTouchStream* soundTouch = getTouch(env, obj);

	delete soundTouch;
}

/*
 * Class:     com_example_soundtouchdemo_NativeSoundTouch
 * Method:    soundTouchgethVersion
 * Signature: ()Ljava/lang/String;
 */
jstring Java_com_audiocap_NativeSoundTouch_soundTouchgethVersion(
		JNIEnv *env, jobject obj) {

	const char *verStr;

    SoundTouchStream* soundTouch = getTouch(env, obj);
	verStr = soundTouch->getVersionString();

	// return version as string
	return env->NewStringUTF(verStr);
}

/*
 * Class:     com_example_soundtouchdemo_NativeSoundTouch
 * Method:    setPitchSemiTones
 * Signature: (D)V
 */
void Java_com_audiocap_NativeSoundTouch_setPitchSemiTones(
		JNIEnv *env, jobject obj, jfloat jpitch) {

	SoundTouchStream* soundTouch = getTouch(env, obj);

	float pitch = jpitch;
	soundTouch->setPitchSemiTones(pitch);
}

/*
 * Class:     com_example_soundtouchdemo_NativeSoundTouch
 * Method:    setTempoChange
 * Signature: (F)V
 */
void Java_com_audiocap_NativeSoundTouch_setTempoChange(
		JNIEnv *env, jobject obj, jfloat jtempo) {

    SoundTouchStream* soundTouch = getTouch(env, obj);

	float tempo = jtempo;
	soundTouch->setTempoChange(tempo);

}

/*
 * Class:     com_example_soundtouchdemo_NativeSoundTouch
 * Method:    shiftingPitch
 * Signature: ([BII)V
 */
void Java_com_audiocap_NativeSoundTouch_shiftingPitch(JNIEnv *env,
		jobject obj, jbyteArray jarray, jint offset, jint length) {

    SoundTouchStream* soundTouch = getTouch(env, obj);

	jbyte *data;
	int bytesPerSample = soundTouch->getBytesPerSample();
    int channel = soundTouch->getChannels();
	int nSamples = length / bytesPerSample ;

	data = env->GetByteArrayElements(jarray, JNI_FALSE);
	soundtouch::SAMPLETYPE* fBufferIn = new soundtouch::SAMPLETYPE[nSamples];

	convertInput(data, fBufferIn, nSamples, bytesPerSample);



	//soundtouch::SAMPLETYPE sampleBuffer[length];
	//memcpy(&sampleBuffer, data, length);

	//(16*1)/8=2bytes,length/2=x;x��sample
//	soundtouch::SAMPLETYPE* fBufferIn = new soundtouch::SAMPLETYPE[BUFF_SIZE];

	soundTouch->putSamples( fBufferIn, nSamples / channel);

	env->ReleaseByteArrayElements(jarray, data, 0);

}

///*
// * Class:     com_example_soundtouchdemo_NativeSoundTouch
// * Method:    receiveSamples
// * Signature: ([BI)I
// */
//jint Java_com_example_soundtouchdemo_NativeSoundTouch_receiveSamples(
//		JNIEnv *env, jobject obj, jbyteArray jarray, jint jLenght) {
//
//    LOGD("begin receiveSamples");
//	int receiveSamples = 0;
//	int maxReceiveSamples = jLenght / (16 / 8);
//
//	soundtouch::SoundTouch* soundTouch = getTouch(env, obj);
//
//	jbyte *data;
//
//	data = env->GetByteArrayElements(jarray, JNI_FALSE);
//
//	receiveSamples = soundTouch->receiveSamples((soundtouch::SAMPLETYPE*) data,
//			maxReceiveSamples);
//
//	//memcpy(data, sampleBuffer, receiveSize);
////    env->SetByteArrayRegion(jarray, 0, receiveSamples * 2, data);
////    jbyteArray  ret = env->NewByteArray(receiveSamples * 2);
////    env->SetByteArrayRegion(ret, 0, receiveSamples * 2, data);
//
//	env->ReleaseByteArrayElements(jarray, data, 0);
////    env->Array
//    LOGD("end receiveSamples");
//
//	return receiveSamples;
//}




//#define buff_size 4096



int Java_com_audiocap_NativeSoundTouch_receiveSamples(JNIEnv *env, jobject obj, jbyteArray jarray,
                                                                           jint jLenght) {

    SoundTouchStream* soundTouch = getTouch(env, obj);
    //LOGD("begin receiveSamples");
    int receiveSamples = 0;
	int bytesPerSample = soundTouch->getBytesPerSample();
    int channels = soundTouch->getChannels();
//    int maxReceiveSamples = jLenght / (16 / 8);
	int maxReceiveSamples = jLenght / bytesPerSample  ;
	jbyte *data;
	data = env->GetByteArrayElements(jarray, JNI_FALSE);

    soundtouch::SAMPLETYPE sampleBuffer[maxReceiveSamples];

    receiveSamples = soundTouch->receiveSamples((soundtouch::SAMPLETYPE*) sampleBuffer,
                                                maxReceiveSamples);


    write(sampleBuffer, receiveSamples * channels, bytesPerSample  , data);
    //memcpy(data, sampleBuffer, receiveSize);
//    env->SetByteArrayRegion(jarray, 0, receiveSamples * 2, data);
//    jbyteArray  ret = env->NewByteArray(receiveSamples * 2);
//    env->SetByteArrayRegion(ret, 0, receiveSamples * 2, (jbyte*)sampleBuffer);

	env->ReleaseByteArrayElements(jarray, data, 0);
//    env->Array
    //LOGD("end receiveSamples new version");

    return receiveSamples;
}


/*
 * Class:     com_example_soundtouchdemo_NativeSoundTouch
 * Method:    soundTouchFlushLastSamples
 * Signature: ()V
 */
void Java_com_audiocap_NativeSoundTouch_soundTouchFlushLastSamples(
		JNIEnv *env, jobject obj) {

	//LOGD("SoundTouch flush");

    SoundTouchStream* soundTouch = getTouch(env, obj);
	soundTouch->flush();
}


static void convertInput(jbyte* input, float* output, const int buffer_size,
						 int bytesPerSample) {
	switch (bytesPerSample) {
		case 1: {
			unsigned char *temp2 = (unsigned char*) input;
			double conv = 1.0 / 128.0;
			for (int i = 0; i < buffer_size; i++) {
				output[i] = (float) (temp2[i] * conv - 1.0);
			}
			break;
		}
		case 2: {
			short *temp2 = (short*) input;
			double conv = 1.0 / 32768.0;
			for (int i = 0; i < buffer_size; i++) {
				short value = temp2[i];
				output[i] = (float) (value * conv);
			}
			break;
		}
		case 3: {
			char *temp2 = (char *) input;
			double conv = 1.0 / 8388608.0;
			for (int i = 0; i < buffer_size; i++) {
				int value = *((int*) temp2);
				value = value & 0x00ffffff;             // take 24 bits
				value |= (value & 0x00800000) ? 0xff000000 : 0; // extend minus sign bits
				output[i] = (float) (value * conv);
				temp2 += 3;
			}
			break;
		}
		case 4: {
			int *temp2 = (int *) input;
			double conv = 1.0 / 2147483648.0;
			assert(sizeof(int) == 4);
			for (int i = 0; i < buffer_size; i++) {
				int value = temp2[i];
				output[i] = (float) (value * conv);
			}
			break;
		}
	}
}


static inline int saturate(float fvalue, float minval, float maxval) {
    if (fvalue > maxval) {
        fvalue = maxval;
    } else if (fvalue < minval) {
        fvalue = minval;
    }
    return (int) fvalue;
}



static void* getConvBuffer(int sizeBytes) {
    int convBuffSize = (sizeBytes + 15) & -8;
    // round up to following 8-byte bounday
    char *convBuff = new char[convBuffSize];
    return convBuff;
}

static int write(const float *bufferIn,  int numElems,
                 int bytesPerSample, jbyte* outdata) {
    int numBytes;

//    int oldSize = bufferOut->size();

    if (numElems == 0)
        return 0;

    numBytes = numElems * bytesPerSample;
    short *temp = (short*) getConvBuffer(numBytes);

    switch (bytesPerSample) {
        case 1: {
            unsigned char *temp2 = (unsigned char *) temp;
            for (int i = 0; i < numElems; i++) {
                temp2[i] = (unsigned char) saturate(bufferIn[i] * 128.0f + 128.0f,
                                                    0.0f, 255.0f);
            }
            break;
        }

        case 2: {
            short *temp2 = (short *) temp;
            for (int i = 0; i < numElems; i++) {
                short value = (short) saturate(bufferIn[i] * 32768.0f, -32768.0f,
                                               32767.0f);
                temp2[i] = value;
            }
            break;
        }

        case 3: {
            char *temp2 = (char *) temp;
            for (int i = 0; i < numElems; i++) {
                int value = saturate(bufferIn[i] * 8388608.0f, -8388608.0f,
                                     8388607.0f);
                *((int*) temp2) = value;
                temp2 += 3;
            }
            break;
        }

        case 4: {
            int *temp2 = (int *) temp;
            for (int i = 0; i < numElems; i++) {
                int value = saturate(bufferIn[i] * 2147483648.0f, -2147483648.0f,
                                     2147483647.0f);
                temp2[i] = value;
            }
            break;
        }
        default:
            //should throw
            break;
    }
    for (int i = 0; i < numBytes / 2; ++i) {
        outdata[2 * i] = temp[i] & 0xff;
        outdata[2*i + 1] = (temp[i] >> 8) & 0xff;
//        bufferOut->push(temp[i] & 0xff);
//        bufferOut->push((temp[i] >> 8) & 0xff);
    }
    delete[] temp;
    temp = NULL;
    return numBytes;
//    return bufferOut->size() - oldSize;
}